// Copyright 2021 Matrix Origin
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package testutil

import (
	"testing"
	"time"

	"github.com/matrixorigin/matrixone/pkg/common/mpool"
	"github.com/matrixorigin/matrixone/pkg/container/nulls"
	"github.com/matrixorigin/matrixone/pkg/container/types"
	"github.com/matrixorigin/matrixone/pkg/container/vector"
)

// All vectors generated by the Make Function, their memory is not allocated through the memory pool
// if you want to generate a vector in memory pool, use NewFunction to instead of MakeFunction.
var (
	TestUtilMp = mpool.MustNewZeroNoFixed()

	MakeBoolVector = func(values []bool, nsp []uint64) *vector.Vector {
		return makeVector(values, nsp, boolType)
	}

	MakeBitVector = func(values []uint64, nsp []uint64) *vector.Vector {
		return makeVector(values, nsp, bitType)
	}

	MakeInt8Vector = func(values []int8, nsp []uint64) *vector.Vector {
		return makeVector(values, nsp, int8Type)
	}

	MakeInt16Vector = func(values []int16, nsp []uint64) *vector.Vector {
		return makeVector(values, nsp, int16Type)
	}

	MakeInt32Vector = func(values []int32, nsp []uint64) *vector.Vector {
		return makeVector(values, nsp, int32Type)
	}

	MakeInt64Vector = func(values []int64, nsp []uint64) *vector.Vector {
		return makeVector(values, nsp, int64Type)
	}

	MakeUint8Vector = func(values []uint8, nsp []uint64) *vector.Vector {
		return makeVector(values, nsp, uint8Type)
	}

	MakeUint16Vector = func(values []uint16, nsp []uint64) *vector.Vector {
		return makeVector(values, nsp, uint16Type)
	}

	MakeUint32Vector = func(values []uint32, nsp []uint64) *vector.Vector {
		return makeVector(values, nsp, uint32Type)
	}

	MakeUint64Vector = func(values []uint64, nsp []uint64) *vector.Vector {
		return makeVector(values, nsp, uint64Type)
	}

	MakeFloat32Vector = func(values []float32, nsp []uint64) *vector.Vector {
		return makeVector(values, nsp, float32Type)
	}

	MakeFloat64Vector = func(values []float64, nsp []uint64) *vector.Vector {
		return makeVector(values, nsp, float64Type)
	}

	MakeRowIdVector = func(values []types.Rowid, nsp []uint64) *vector.Vector {
		return makeVector(values, nsp, rowIdType)
	}

	MakeBlockIdVector = func(values []types.Blockid, nsp []uint64) *vector.Vector {
		return makeVector(values, nsp, blockIdType)
	}

	MakeTSVector = func(values []types.TS, nsp []uint64) *vector.Vector {
		return makeVector(values, nsp, tsType)
	}

	MakeVarcharVector = func(values []string, nsp []uint64) *vector.Vector {
		return makeStringVector(values, nsp, varcharType)
	}

	MakeTextVector = func(values []string, nsp []uint64) *vector.Vector {
		return makeStringVector(values, nsp, textType)
	}

	MakeUUIDVector = func(values []types.Uuid, nsp []uint64) *vector.Vector {
		return makeVector(values, nsp, uuidType)
	}

	MakeJsonVector = func(values []string, nsp []uint64) *vector.Vector {
		return makeJsonVector(values, nsp)
	}

	MakeDateVector = func(values []string, nsp []uint64) *vector.Vector {
		return makeDateVector(values, nsp)
	}

	MakeTimeVector = func(values []string, nsp []uint64) *vector.Vector {
		return makeTimeVector(values, nsp)
	}

	MakeDatetimeVector = func(values []string, nsp []uint64) *vector.Vector {
		return makeDatetimeVector(values, nsp)
	}

	MakeTimestampVector = func(values []string, nsp []uint64) *vector.Vector {
		return makeTimestampVector(values, nsp)
	}
)

// functions to make a scalar vector for test.
var (
	MakeScalarNull = func(t testing.TB, typ types.T, length int) *vector.Vector {
		return vector.NewConstNull(typ.ToType(), length, NewProc(t).Mp())
	}
	MakeScalarInt64 = func(v int64, length int) *vector.Vector {
		return makeScalar(v, length, int64Type)
	}

	MakeScalarVarchar = func(value string, length int) *vector.Vector {
		return makeScalarString(value, length, varcharType)
	}
)

func makeVector[T types.FixedSizeT](values []T, nsp []uint64, typ types.Type) *vector.Vector {
	vec := vector.NewVec(typ)
	err := vector.AppendFixedList(vec, values, nil, TestUtilMp)
	vec.SetNulls(nulls.Build(len(values), nsp...))

	if err != nil {
		panic(err)
	}
	return vec
}

func makeStringVector(values []string, nsp []uint64, typ types.Type) *vector.Vector {
	var err error

	vec := vector.NewVec(typ)
	if nsp == nil {
		err = vector.AppendStringList(vec, values, nil, TestUtilMp)
	} else {
		err = vector.AppendStringList(vec, values, nil, TestUtilMp)
		vec.SetNulls(nulls.Build(len(values), nsp...))
	}
	if err != nil {
		panic(err)
	}

	return vec
}

func makeJsonVector(values []string, nsp []uint64) *vector.Vector {
	var err error

	newVals := make([][]byte, len(values))
	for i := range values {
		json, err := types.ParseStringToByteJson(values[i])
		if err != nil {
			panic(err)
		}
		newVals[i], err = types.EncodeJson(json)
		if err != nil {
			panic(err)
		}
	}

	vec := vector.NewVec(jsonType)
	if nsp == nil {
		err = vector.AppendBytesList(vec, newVals, nil, TestUtilMp)
	} else {
		err = vector.AppendBytesList(vec, newVals, nil, TestUtilMp)
		vec.SetNulls(nulls.Build(len(values), nsp...))
	}
	if err != nil {
		panic(err)
	}

	return vec
}

func makeDateVector(values []string, nsp []uint64) *vector.Vector {
	var err error

	newVals := make([]types.Date, len(values))
	for i := range values {
		dt, err := types.ParseDatetime(values[i], 0)
		if err != nil {
			panic(err)
		}
		newVals[i] = dt.ToDate()
	}

	vec := vector.NewVec(dateType)
	err = vector.AppendFixedList(vec, newVals, nil, TestUtilMp)
	vec.SetNulls(nulls.Build(len(values), nsp...))
	if err != nil {
		panic(err)
	}

	return vec
}

func makeTimeVector(values []string, nsp []uint64) *vector.Vector {
	var err error

	newVals := make([]types.Time, len(values))
	for i := range values {
		dt, err := types.ParseDatetime(values[i], 0)
		if err != nil {
			panic(err)
		}
		newVals[i] = dt.ToTime(6)
	}

	vec := vector.NewVec(timeType)
	err = vector.AppendFixedList(vec, newVals, nil, TestUtilMp)
	vec.SetNulls(nulls.Build(len(values), nsp...))
	if err != nil {
		panic(err)
	}

	return vec
}

func makeDatetimeVector(values []string, nsp []uint64) *vector.Vector {
	var err error

	newVals := make([]types.Datetime, len(values))
	for i := range values {
		dt, err := types.ParseDatetime(values[i], 0)
		if err != nil {
			panic(err)
		}
		newVals[i] = dt
	}

	vec := vector.NewVec(datetimeType)
	err = vector.AppendFixedList(vec, newVals, nil, TestUtilMp)
	vec.SetNulls(nulls.Build(len(values), nsp...))
	if err != nil {
		panic(err)
	}

	return vec
}

func makeTimestampVector(values []string, nsp []uint64) *vector.Vector {
	var err error

	newVals := make([]types.Timestamp, len(values))
	for i := range values {
		dt, err := types.ParseDatetime(values[i], 0)
		if err != nil {
			panic(err)
		}
		newVals[i] = dt.ToTimestamp(time.UTC)
	}

	vec := vector.NewVec(timestampType)
	err = vector.AppendFixedList(vec, newVals, nil, TestUtilMp)
	vec.SetNulls(nulls.Build(len(values), nsp...))
	if err != nil {
		panic(err)
	}

	return vec
}

func makeScalar[T types.FixedSizeT](value T, length int, typ types.Type) *vector.Vector {
	v, err := vector.NewConstFixed(typ, value, length, TestUtilMp)
	if err != nil {
		panic(err)
	}
	return v
}

func makeScalarString(value string, length int, typ types.Type) *vector.Vector {
	v, err := vector.NewConstBytes(typ, []byte(value), length, TestUtilMp)
	if err != nil {
		panic(err)
	}
	return v
}

func MakeVarlenaVector(data [][]byte, nulls []uint64, mp *mpool.MPool) *vector.Vector {
	vec := vector.NewVec(types.T_blob.ToType())
	for _, d := range data {
		if d != nil {
			vector.AppendBytes(vec, d, false, mp)
		} else {
			vector.AppendBytes(vec, nil, true, mp)
		}
	}

	if len(nulls) > 0 {
		vecNsp := vec.GetNulls()
		for _, row := range nulls {
			vecNsp.Add(row)
		}
	}

	return vec
}
